package com.siebre.picclife.asynctask;

import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.io.IOException;

import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.google.common.base.Suppliers;
import com.siebre.picclife.asynctask.PersistenceConfig.DatabaseNameProvider;
import com.siebre.picclife.asynctask.annotation.AsyncTaskProxy;
import com.siebre.picclife.asynctask.annotation.TaskMapping;
import com.siebre.picclife.asynctask.model.Task;
import com.siebre.picclife.asynctask.model.TaskStatus;
import com.siebre.picclife.asynctask.service.TaskRepository;
import com.siebre.picclife.asynctask.service.support.ArgumentDeserializer;
import com.siebre.picclife.asynctask.service.support.ArgumentSerializer;
import com.siebre.picclife.asynctask.service.support.TaskArgumentHandlerRegistrar;
import com.siebre.picclife.asynctask.service.support.TaskArgumentHandlerRegistry;
import com.siebre.picclife.asynctask.springconfig.TaskCreationConfig;
import com.siebre.picclife.asynctask.springconfig.TaskProcessConfig;
import com.siebre.picclife.asynctask.springcontext.annotation.ActivateAsyncTask;
import com.siebre.policy.InsurancePolicy;
import com.siebre.policy.InsurancePolicyImpl;
import com.siebre.policy.dao.InsurancePolicyRepository;
import com.siebre.repository.local.DefaultLocalDataStore;
import com.siebre.repository.test.LocalRepositoryAnswer;

@Test
@ContextConfiguration
public class AsyncTaskDemo extends AbstractTestNGSpringContextTests {

	@Autowired
	private InsurancePolicyRepository policyRepository;
	
	@Autowired
	private TaskRepository taskRepository;
	
	@Autowired
	@Qualifier("policyServiceProxy")
	private PolicyService serviceProxy;
	
	@Autowired
	private PolicyService targetService;
	
	@BeforeMethod
	public void reset() {
		Mockito.reset(targetService);
	}
	
	public void testSyncNewPolicy() throws InterruptedException, IOException {
		InsurancePolicyImpl policy = new InsurancePolicyImpl();
		policy.setPolicyNumber("POL001");
		policyRepository.save(policy);
		serviceProxy.syncNewPolicy(policy);
		Thread.sleep(2000);
		
		Task task = taskRepository.findByArgument("POL001").get(0);
		
		assertThat(task.getStatus()).isEqualTo(TaskStatus.SUCCEEDED);

		verify(targetService).syncNewPolicy(policy);
	}
	
	public void testCancelPolicy() throws InterruptedException {
		InsurancePolicyImpl policy = new InsurancePolicyImpl();
		policy.setPolicyNumber("POL002");
		policyRepository.save(policy);
		serviceProxy.cancelPolicy(policy);
		Thread.sleep(2000);

		Task task = taskRepository.findByArgument("POL002").get(0);
		
		assertThat(task.getStatus()).isEqualTo(TaskStatus.SUCCEEDED);
		
		verify(targetService).cancelPolicy(policy);
	}
	
	public void testRestart() throws InterruptedException, IOException {
		InsurancePolicyImpl policy = new InsurancePolicyImpl();
		policy.setPolicyNumber("POL003");
		policyRepository.save(policy);
		
		doThrow(IOException.class).when(targetService).syncNewPolicy(policy);
		
		serviceProxy.syncNewPolicy(policy);
		Thread.sleep(3000);
//		System.in.read();
		
		Task task = taskRepository.findByArgument("POL003").get(0);
		
		assertThat(task.getStatus()).isEqualTo(TaskStatus.FAILED);
		assertThat(task.getFailureCount()).isEqualTo(2);
		assertThat(task.lastProcessStats().getResultMessage()).isNotEmpty();
		
		verify(targetService, times(2)).syncNewPolicy(policy);
	}
	
	public void testSimpleParameter() throws InterruptedException {
		serviceProxy.syncNewPolicyByNumber("POL004");
		
		Thread.sleep(2000);
		
		verify(targetService).syncNewPolicyByNumber("POL004");
	}
	
	@AsyncTaskProxy
	public static interface PolicyService {
		
		@TaskMapping(type = "new-business")
		void syncNewPolicy(InsurancePolicy policy);

		@TaskMapping(type = "cancel-policy")
		void cancelPolicy(InsurancePolicy policy);
		
		void syncNewPolicyByNumber(String policyNumber);
	}
	
	@Configuration
	@ActivateAsyncTask("com.siebre.picclife.asynctask")
	@Import({
		PersistenceConfig.class,
		TaskCreationConfig.class, TaskProcessConfig.class})
	@PropertySource("config/test.properties")
	static class Config {
		
		@Bean
		static PropertySourcesPlaceholderConfigurer psphConfigurer() {
			return new PropertySourcesPlaceholderConfigurer();
		}
		
		@Bean
		DatabaseNameProvider databaseNameProvider() {
			return new DatabaseNameProvider(Suppliers.ofInstance(AsyncTaskDemo.class.getSimpleName()));
		}
	}
	
	@Configuration
	static class PolicyServiceConfig {
		
		@Bean
		TaskArgumentHandlerRegistrar policyArgumentHandlerRegistrar() {
			return new TaskArgumentHandlerRegistrar() {

				public void register(TaskArgumentHandlerRegistry registry) {
					ArgumentSerializer serializer = new ArgumentSerializer() {
						
						public String serialize(Object argument) {
							return ((InsurancePolicy) argument).getPolicyNumber();
						}
					};
					ArgumentDeserializer deserializer = new ArgumentDeserializer() {

						public Object deserialize(String serialized, Class<?> type) {
							return policyRepository().findByPolicyNumber(serialized);
						}
					};
					registry.register(InsurancePolicy.class, serializer, deserializer);
				}
			};
		}
		
		@Bean
		InsurancePolicyRepository policyRepository() {
			return mock(InsurancePolicyRepository.class, LocalRepositoryAnswer.from(new DefaultLocalDataStore<Object>()));
		}
		
		@Bean
		@Primary
		PolicyService targetService() {
			return mock(PolicyService.class);
		}
	}
}
